Buka kekuatan React Portals untuk membuat modal dan tooltip yang mudah diakses dan menarik secara visual, meningkatkan pengalaman pengguna dan struktur komponen.
React Portals: Menguasai Modal dan Tooltip untuk UX yang Lebih Baik
Dalam pengembangan web modern, membuat antarmuka pengguna yang intuitif dan menarik sangatlah penting. React, sebuah pustaka JavaScript populer untuk membangun antarmuka pengguna, menyediakan berbagai alat dan teknik untuk mencapai hal ini. Salah satu alat yang sangat berguna adalah React Portals. Postingan blog ini membahas dunia React Portals, berfokus pada penerapannya dalam membangun modal dan tooltip yang mudah diakses dan menarik secara visual.
Apa itu React Portals?
React Portals menawarkan cara untuk merender elemen anak komponen ke dalam node DOM yang berada di luar hierarki DOM komponen induk. Sederhananya, ini memungkinkan Anda untuk membebaskan diri dari pohon komponen React standar dan memasukkan elemen langsung ke bagian lain dari struktur HTML. Ini sangat berguna untuk situasi di mana Anda perlu mengontrol konteks penumpukan atau memposisikan elemen di luar batas wadah induknya.
Secara tradisional, komponen React dirender sebagai elemen anak dari komponen induknya di dalam DOM. Ini terkadang dapat menyebabkan tantangan gaya dan tata letak, terutama ketika berhadapan dengan elemen seperti modal atau tooltip yang perlu muncul di atas konten lain atau diposisikan relatif terhadap viewport. React Portals memberikan solusi dengan memungkinkan elemen-elemen ini dirender langsung ke bagian lain dari pohon DOM, melewati batasan-batasan ini.
Mengapa Menggunakan React Portals?
Beberapa manfaat utama menjadikan React Portals sebagai alat yang berharga dalam gudang pengembangan React Anda:
- Peningkatan Gaya dan Tata Letak: Portal memungkinkan Anda memposisikan elemen di luar wadah induknya, mengatasi masalah gaya yang disebabkan oleh
overflow: hidden, batasanz-index, atau kendala tata letak yang kompleks. Bayangkan sebuah modal yang perlu menutupi seluruh layar, bahkan jika wadah induknya memilikioverflow: hiddenyang diatur. Portal memungkinkan Anda merender modal langsung ke dalambody, melewati batasan ini. - Aksesibilitas yang Ditingkatkan: Portal sangat penting untuk aksesibilitas, terutama saat berhadapan dengan modal. Merender konten modal langsung ke dalam
bodymemungkinkan Anda dengan mudah mengelola penangkapan fokus, memastikan bahwa pengguna yang menggunakan pembaca layar atau navigasi keyboard tetap berada di dalam modal saat terbuka. Ini penting untuk memberikan pengalaman pengguna yang mulus dan mudah diakses. - Struktur Komponen yang Lebih Bersih: Dengan merender konten modal atau tooltip di luar pohon komponen utama, Anda dapat menjaga struktur komponen Anda lebih bersih dan lebih mudah dikelola. Pemisahan masalah ini dapat membuat kode Anda lebih mudah dibaca, dipahami, dan dipelihara.
- Menghindari Masalah Konteks Penumpukan: Konteks penumpukan dalam CSS bisa sangat sulit dikelola. Portal membantu Anda menghindari masalah ini dengan memungkinkan Anda merender elemen langsung ke akar DOM, memastikan mereka selalu diposisikan dengan benar relatif terhadap elemen lain di halaman.
Menerapkan Modal dengan React Portals
Modal adalah pola UI umum yang digunakan untuk menampilkan informasi penting atau meminta input dari pengguna. Mari kita jelajahi cara membuat modal menggunakan React Portals.
1. Membuat Akar Portal
Pertama, Anda perlu membuat node DOM tempat modal akan dirender. Ini biasanya dilakukan dengan menambahkan elemen div dengan ID tertentu ke file HTML Anda (biasanya di dalam body):
<div id="modal-root"></div>
2. Membuat Komponen Modal
Selanjutnya, buat komponen React yang mewakili modal. Komponen ini akan berisi konten dan logika modal.
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, onClose, children }) => {
const [mounted, setMounted] = useState(false);
const modalRoot = useRef(document.getElementById('modal-root'));
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
if (!isOpen) return null;
const modalContent = (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>
);
return mounted && modalRoot.current
? ReactDOM.createPortal(modalContent, modalRoot.current)
: null;
};
export default Modal;
Penjelasan:
isOpenprop: Menentukan apakah modal terlihat.onCloseprop: Fungsi untuk menutup modal.childrenprop: Konten yang akan ditampilkan di dalam modal.modalRootref: Mereferensikan node DOM tempat modal akan dirender (#modal-root).useEffecthook: Memastikan modal hanya dirender setelah komponen dipasang untuk menghindari masalah dengan akar portal yang tidak langsung tersedia.ReactDOM.createPortal: Ini adalah kunci untuk menggunakan React Portals. Dibutuhkan dua argumen: elemen React yang akan dirender (modalContent) dan node DOM tempat elemen itu harus dirender (modalRoot.current).- Mengklik overlay: Menutup modal. Kami menggunakan
e.stopPropagation()pada divmodal-contentuntuk mencegah klik di dalam modal menutupnya.
3. Menggunakan Komponen Modal
Sekarang, Anda dapat menggunakan komponen Modal di aplikasi Anda:
import React, { useState } from 'react';
import Modal from './Modal';
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<div>
<button onClick={openModal}>Open Modal</button>
<Modal isOpen={isModalOpen} onClose={closeModal}>
<h2>Modal Content</h2>
<p>This is the content of the modal.</p>
</Modal>
</div>
);
};
export default App;
Contoh ini menunjukkan cara mengontrol visibilitas modal menggunakan prop isOpen dan fungsi openModal dan closeModal. Konten di dalam tag <Modal> akan dirender di dalam modal.
4. Menata Gaya Modal
Tambahkan gaya CSS untuk memposisikan dan menata gaya modal. Berikut adalah contoh dasar:
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Latar belakang semi-transparan */
display: flex;
justify-content: center;
align-items: center;
z-index: 1000; /* Pastikan itu di atas konten lain */
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
Penjelasan CSS:
position: fixed: Memastikan modal menutupi seluruh viewport, terlepas dari scrolling.background-color: rgba(0, 0, 0, 0.5): Membuat overlay semi-transparan di belakang modal.display: flex, justify-content: center, align-items: center: Memusatkan modal secara horizontal dan vertikal.z-index: 1000: Memastikan modal dirender di atas semua elemen lain di halaman.
5. Pertimbangan Aksesibilitas untuk Modal
Aksesibilitas sangat penting saat menerapkan modal. Berikut adalah beberapa pertimbangan utama:- Manajemen Fokus: Ketika modal terbuka, fokus harus secara otomatis dipindahkan ke elemen di dalam modal (misalnya, bidang input pertama atau tombol tutup). Ketika modal ditutup, fokus harus kembali ke elemen yang memicu pembukaan modal. Ini sering dicapai menggunakan hook
useRefReact untuk menyimpan elemen yang sebelumnya difokuskan. - Navigasi Keyboard: Pastikan pengguna dapat menavigasi modal menggunakan keyboard (tombol Tab). Fokus harus terperangkap di dalam modal, mencegah pengguna secara tidak sengaja menekan tab keluar darinya. Pustaka seperti
react-focus-lockdapat membantu dengan ini. - Atribut ARIA: Gunakan atribut ARIA untuk memberikan informasi semantik tentang modal ke pembaca layar. Misalnya, gunakan
aria-modal="true"pada wadah modal danaria-labelatauaria-labelledbyuntuk memberikan label deskriptif untuk modal. - Mekanisme Penutupan: Sediakan beberapa cara untuk menutup modal, seperti tombol tutup, mengklik overlay, atau menekan tombol Escape.
Contoh Manajemen Fokus (menggunakan useRef):
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, onClose, children }) => {
const [mounted, setMounted] = useState(false);
const modalRoot = useRef(document.getElementById('modal-root'));
const firstFocusableElement = useRef(null);
const previouslyFocusedElement = useRef(null);
useEffect(() => {
setMounted(true);
if (isOpen) {
previouslyFocusedElement.current = document.activeElement;
if (firstFocusableElement.current) {
firstFocusableElement.current.focus();
}
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
if (previouslyFocusedElement.current) {
previouslyFocusedElement.current.focus();
}
};
}
return () => setMounted(false);
}, [isOpen, onClose]);
if (!isOpen) return null;
const modalContent = (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<h2>Modal Content</h2>
<p>This is the content of the modal.</p>
<input type="text" ref={firstFocusableElement} /> <!-- First focusable element -->
<button onClick={onClose}>Close</button>
</div>
</div>
);
return mounted && modalRoot.current
? ReactDOM.createPortal(modalContent, modalRoot.current)
: null;
};
export default Modal;
Penjelasan Kode Manajemen Fokus:
previouslyFocusedElement.current: Menyimpan elemen yang memiliki fokus sebelum modal dibuka.firstFocusableElement.current: Mengacu pada elemen pertama yang dapat difokuskan *di dalam* modal (dalam contoh ini, input teks).- Ketika modal terbuka (
isOpenbernilai true):- Elemen yang saat ini difokuskan disimpan.
- Fokus dipindahkan ke
firstFocusableElement.current. - Event listener ditambahkan untuk mendengarkan tombol Escape, menutup modal.
- Ketika modal ditutup (fungsi pembersihan):
- Event listener tombol Escape dihapus.
- Fokus dikembalikan ke elemen yang sebelumnya difokuskan.
Menerapkan Tooltip dengan React Portals
Tooltip adalah popup informasi kecil yang muncul saat pengguna mengarahkan kursor ke atas elemen. React Portals dapat digunakan untuk membuat tooltip yang diposisikan dengan benar, terlepas dari gaya atau tata letak elemen induk.
1. Membuat Akar Portal (jika belum dibuat)
Jika Anda belum membuat akar portal untuk modal, tambahkan elemen div dengan ID tertentu ke file HTML Anda (biasanya di dalam body):
<div id="tooltip-root"></div>
2. Membuat Komponen Tooltip
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
const Tooltip = ({ text, children, position = 'top' }) => {
const [isVisible, setIsVisible] = useState(false);
const [positionStyle, setPositionStyle] = useState({});
const [mounted, setMounted] = useState(false);
const tooltipRoot = useRef(document.getElementById('tooltip-root'));
const tooltipRef = useRef(null);
const triggerRef = useRef(null);
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
const handleMouseEnter = () => {
setIsVisible(true);
updatePosition();
};
const handleMouseLeave = () => {
setIsVisible(false);
};
const updatePosition = () => {
if (!triggerRef.current || !tooltipRef.current) return;
const triggerRect = triggerRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
let top = 0;
let left = 0;
switch (position) {
case 'top':
top = triggerRect.top - tooltipRect.height - 5; // Spasi 5px
left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
break;
case 'bottom':
top = triggerRect.bottom + 5;
left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
break;
case 'left':
top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
left = triggerRect.left - tooltipRect.width - 5;
break;
case 'right':
top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
left = triggerRect.right + 5;
break;
default:
break;
}
setPositionStyle({
top: `${top}px`,
left: `${left}px`,
});
};
const tooltipContent = isVisible && (
<div className="tooltip" style={positionStyle} ref={tooltipRef}>
{text}
</div>
);
return (
<span
ref={triggerRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{children}
{mounted && tooltipRoot.current ? ReactDOM.createPortal(tooltipContent, tooltipRoot.current) : null}
</span>
);
};
export default Tooltip;
Penjelasan:
textprop: Teks yang akan ditampilkan di tooltip.childrenprop: Elemen yang memicu tooltip (elemen yang dihover oleh pengguna).positionprop: Posisi tooltip relatif terhadap elemen pemicu ('top', 'bottom', 'left', 'right'). Default ke 'top'.isVisiblestate: Mengontrol visibilitas tooltip.tooltipRootref: Mereferensikan node DOM tempat tooltip akan dirender (#tooltip-root).tooltipRefref: Mereferensikan elemen tooltip itu sendiri, digunakan untuk menghitung dimensinya.triggerRefref: Mereferensikan elemen yang memicu tooltip (children).handleMouseEnterdanhandleMouseLeave: Event handler untuk mengarahkan kursor ke atas elemen pemicu.updatePosition: Menghitung posisi yang benar dari tooltip berdasarkan proppositiondan dimensi elemen pemicu dan tooltip. Ia menggunakangetBoundingClientRect()untuk mendapatkan posisi dan dimensi elemen relatif terhadap viewport.ReactDOM.createPortal: Merender konten tooltip ke dalamtooltipRoot.
3. Menggunakan Komponen Tooltip
import React from 'react';
import Tooltip from './Tooltip';
const App = () => {
return (
<div>
<p>
Hover over this <Tooltip text="This is a tooltip!\nWith multiple lines." position="bottom">text</Tooltip> to see a tooltip.
</p>
<button>
Hover <Tooltip text="Button tooltip" position="top">here</Tooltip> for tooltip.
</button>
</div>
);
};
export default App;
Contoh ini menunjukkan cara menggunakan komponen Tooltip untuk menambahkan tooltip ke teks dan tombol. Anda dapat menyesuaikan teks dan posisi tooltip menggunakan prop text dan position.
4. Menata Gaya Tooltip
Tambahkan gaya CSS untuk memposisikan dan menata gaya tooltip. Berikut adalah contoh dasar:
.tooltip {
position: absolute;
background-color: rgba(0, 0, 0, 0.8); /* Latar belakang gelap */
color: white;
padding: 5px;
border-radius: 3px;
font-size: 12px;
z-index: 1000; /* Pastikan itu di atas konten lain */
white-space: pre-line; /* Menghormati jeda baris dalam properti teks */
}
Penjelasan CSS:
position: absolute: Memposisikan tooltip relatif terhadaptooltip-root. FungsiupdatePositiondalam komponen React menghitung nilaitopdanleftyang tepat untuk memposisikan tooltip di dekat elemen pemicu.background-color: rgba(0, 0, 0, 0.8): Membuat latar belakang gelap yang sedikit transparan untuk tooltip.white-space: pre-line: Ini penting untuk menjaga jeda baris yang mungkin Anda sertakan dalam proptext. Tanpa ini, teks tooltip akan muncul dalam satu baris.
Pertimbangan Global dan Praktik Terbaik
Saat mengembangkan aplikasi React untuk audiens global, pertimbangkan praktik terbaik ini:
- Internasionalisasi (i18n): Gunakan pustaka seperti
react-i18nextatauFormatJSuntuk menangani terjemahan dan lokalisasi. Ini memungkinkan Anda untuk dengan mudah mengadaptasi aplikasi Anda ke berbagai bahasa dan wilayah. Untuk modal dan tooltip, pastikan konten teks diterjemahkan dengan benar. - Dukungan Kanan-ke-Kiri (RTL): Untuk bahasa yang dibaca dari kanan ke kiri (misalnya, Arab, Ibrani), pastikan modal dan tooltip Anda ditampilkan dengan benar. Anda mungkin perlu menyesuaikan posisi dan gaya elemen untuk mengakomodasi tata letak RTL. Properti logis CSS (misalnya,
margin-inline-startalih-alihmargin-left) dapat membantu. - Sensitivitas Budaya: Berhati-hatilah terhadap perbedaan budaya saat mendesain modal dan tooltip Anda. Hindari menggunakan gambar atau simbol yang mungkin ofensif atau tidak pantas dalam budaya tertentu.
- Zona Waktu dan Format Tanggal: Jika modal atau tooltip Anda menampilkan tanggal atau waktu, pastikan modal tersebut diformat sesuai dengan lokal dan zona waktu pengguna. Pustaka seperti
moment.js(walaupun sudah lama, masih banyak digunakan) ataudate-fnsdapat membantu dengan ini. - Aksesibilitas untuk Berbagai Kemampuan: Patuhi pedoman aksesibilitas (WCAG) untuk memastikan bahwa modal dan tooltip Anda dapat digunakan oleh orang-orang dengan disabilitas. Ini termasuk menyediakan teks alternatif untuk gambar, memastikan kontras warna yang memadai, dan menyediakan dukungan navigasi keyboard.
Kesimpulan
React Portals adalah alat yang ampuh untuk membangun antarmuka pengguna yang fleksibel dan mudah diakses. Dengan memahami cara menggunakannya secara efektif, Anda dapat membuat modal dan tooltip yang meningkatkan pengalaman pengguna dan meningkatkan struktur dan pemeliharaan aplikasi React Anda. Ingatlah untuk memprioritaskan aksesibilitas dan pertimbangan global saat mengembangkan untuk audiens yang beragam, memastikan bahwa aplikasi Anda inklusif dan dapat digunakan oleh semua orang.